package com.cnwidsom.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.cnwidsom.filter.FilterChain;
import com.cnwidsom.filter.FilterChainFactory;
import com.cnwidsom.filter.FilterConfig;
import com.cnwidsom.filter.FilterUtils;

public class MonitorConfig {
	private static MonitorConfig cur_mc = null;

	public static final String MONITOR_TYPE_DEFAULT = "custom";
	public static final String RECORD_PARAMETERS_TYPE_ALWAYS = "always";// 总是记录参数
	public static final String RECORD_PARAMETERS_TYPE_WITHERROR = "withError";// 发生错误时记录
	public static final String RECORD_PARAMETERS_TYPE_NEVER = "never";// 不记录参数
	public static final int RECORD_TYPE_OPTION = 0;
	public static final int RECORD_TYPE_FORCE = 1;
	public static final String CALL_TYPE_LOCAL = "local";// 本地调用
	// public static final String CALL_REMOTE = "call_remote";//
	// 远程调用(必须为RPC接口调用方式,传递参数中至少有一个实现Serializable接口)
	// public static final String CALLED_BY_REMOTE = "called_by_remote";//
	// 远程被调用(必须为RPC接口调用方式,接收参数中至少有一个实现Serializable接口)
	public static final String DATA_TYPE_FUNCTION = "function";// 普通函数
	public static final String DATA_TYPE_REMOTE_ENTITY = "remote_entity";// 暂无效果

	// 结点名称,多结点提供服务时用于区分调用是在哪里发生的
	private String nodeName;
	// 监控项配置
	private List<MonitorItemConfig> includes = new ArrayList<MonitorItemConfig>();
	// 监控排除的路径(必须设置到方法,可以使用*通配符,不区分重载函数)
	private List<String> excludes = new ArrayList<String>();
	// 处理过后的排除路径,将路径转换为正则表达式
	private List<Pattern> exclude_pattern = new ArrayList<Pattern>();
	// 监控起始方法(必须设置到方法,可以使用*通配符,不区分重载函数)
	private Map<String, Map<String, Pattern>> startPaths = new LinkedHashMap<String, Map<String, Pattern>>();

	// 调用时间超时告警默认设置（只记录超过指定时间的调用路径）
	private long defaultRecordTime = Integer.MAX_VALUE;
	// 默认是否记录调用参数信息
	private String isDefaultRecordParameters = RECORD_PARAMETERS_TYPE_WITHERROR;
	// 默认记录详细异常信息（开启则会记录异常堆栈信息和参数信息）
	private boolean isDefaultRecordExceptionDetail = false;
	// 监控类型
	private List<String> monitorTypes = new ArrayList<String>();
	// 监控类型扩展参数
	private List<Map<String, String>> monitorTypeParam = new ArrayList<Map<String, String>>();
	// 对于由代理生成的类(如CGLib动态生成),是否也进行监控
	private boolean isDefaultMonitorProxyClass = false;
	// 数据保存方式
	private String persistenceType;
	// 数据保存扩展参数
	private Map<String, String> persistenceTypeParam = new HashMap<String, String>();
	// 日志记录间隔
	private long logInterval;
	// 强制记录每次调用信息(否则按照defaultRecordTime和isDefaultRecordExceptionDetail配置进行记录)
	private int defaultForceRecord = RECORD_TYPE_OPTION;
	// 过滤器配置
	private Map<Integer, FilterChain> filterChains = new HashMap<Integer, FilterChain>();

	// 生成参数,用来统计扫描类前缀

	private MonitorConfig() {

	}

	private static Pattern buildPathPattern(String path) {
		path = path.replace("\\", "\\\\");
		path = path.replace(".", "\\.");
		path = path.replace("$", "\\$");
		path = path.replace("*", "(.*)");
		Pattern p = Pattern.compile("^" + path + "$");
		return p;
	}

	/**
	 * 获得设置path的类pattern,如设置扫描路径为com.test.class.*.method*,那么返回的类Pattern为^com\.
	 * test\.class\.(.*)
	 * 
	 * @param path
	 * @return
	 */
	public static Pattern buildClassPackagePattern(String path) {
		path = path.substring(0, path.lastIndexOf("."));
		return buildPathPattern(path);
	}

	public static class MonitorItemConfig implements Serializable {
		// 路径(必须设置到方法,可以使用*通配符,不区分重载函数)
		private String path;
		private Pattern path_pattern;
		// 调用时间超时告警设置(不设置此项则使用默认设置)
		private Long recordTime = null;
		// 是否记录调用参数信息(不设置此项则使用默认设置)
		private String isRecordParameters = null;
		// 是否记录详细异常信息（开启则会记录异常堆栈信息和参数信息，不设置此项则使用默认设置）
		private Boolean isRecordExceptionDetail = null;
		// 强制记录每次调用信息(否则按照recordTime和isRecordExceptionDetail配置进行记录)
		private Integer forceRecord = RECORD_TYPE_OPTION;
		// 调用类型,根据调用方式不同代理方式也不一样
		private String callType = CALL_TYPE_LOCAL;
		// include类型,如果为远程调用参数则增加REMOTE_CALL_ID字段,而不记录调用过程
		private String type = DATA_TYPE_FUNCTION;
		// 是否监控代理类
		private Boolean isMonitorProxyClass = false;

		public Boolean getIsRecordExceptionDetail() {
			return isRecordExceptionDetail;
		}

		public void setIsRecordExceptionDetail(Boolean isRecordExceptionDetail) {
			this.isRecordExceptionDetail = isRecordExceptionDetail;
		}

		public Boolean isMonitorProxyClass() {
			return isMonitorProxyClass;
		}

		public void setMonitorProxyClass(Boolean isMonitorProxyClass) {
			this.isMonitorProxyClass = isMonitorProxyClass;
		}

		public String getPath() {
			return path;
		}

		public void setPath(String path) {
			this.path = path;
		}

		public Long getRecordTime() {
			return recordTime;
		}

		public void setRecordTime(Long recordTime) {
			this.recordTime = recordTime;
		}

		public String getIsRecordParameters() {
			return isRecordParameters;
		}

		public void setIsRecordParameters(String isRecordParameters) {
			this.isRecordParameters = isRecordParameters;
		}

		public Boolean isRecordExceptionDetail() {
			return isRecordExceptionDetail;
		}

		public void setRecordExceptionDetail(Boolean isRecordExceptionDetail) {
			this.isRecordExceptionDetail = isRecordExceptionDetail;
		}

		public Pattern getPath_pattern() {
			return path_pattern;
		}

		public void setPath_pattern(Pattern path_pattern) {
			this.path_pattern = path_pattern;
		}

		public Integer getForceRecord() {
			return forceRecord;
		}

		public void setForceRecord(Integer forceRecord) {
			this.forceRecord = forceRecord;
		}

		public String getCallType() {
			return callType;
		}

		public void setCallType(String callType) {
			this.callType = callType;
		}

		public String getType() {
			return type;
		}

		public void setType(String type) {
			this.type = type;
		}

	}

	public static MonitorConfig initDefault() {
		MonitorConfig mc = new MonitorConfig();
		return mc;
	}

	public MonitorItemConfig getMatchRule(String path) {
		for (MonitorItemConfig mic : includes) {
			if (mic.getPath_pattern().matcher(path).matches()) {
				return mic;
			}
		}
		return null;
	}

	public static MonitorConfig parseConfig(ClassLoader loader, String path)
			throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
		InputStream is = null;
		if (path.startsWith("classpath:")) {
			path = path.substring("classpath:".length());
			is = loader.getResourceAsStream(path);
		} else {
			is = new FileInputStream(new File(path));
		}
		MonitorConfig mc = new MonitorConfig();
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setValidating(false);
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.parse(is);

		// 创建XPath对象
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		Node node = null;
		mc.setMonitorTypes(new ArrayList<String>());
		mc.setMonitorTypeParam(new ArrayList<Map<String, String>>());
		NodeList nodeList = (NodeList) xpath.evaluate("/config/monitorTypes/*", doc, XPathConstants.NODESET);
		for (int i = 0; i < nodeList.getLength(); i++) {
			node = (Node) xpath.evaluate("/config/monitorTypes/monitorType[" + (i + 1) + "]", doc, XPathConstants.NODE);

			Map<String, String> attrs = new HashMap<String, String>();
			for (int k = 0; k < node.getAttributes().getLength(); k++) {
				Node attrNode = node.getAttributes().item(k);
				attrs.put(attrNode.getNodeName(), attrNode.getTextContent());
			}

			mc.getMonitorTypes().add(node.getTextContent());
			mc.getMonitorTypeParam().add(attrs);
		}

		node = (Node) xpath.evaluate("/config/nodeName", doc, XPathConstants.NODE);
		mc.setNodeName(node.getTextContent());
		node = (Node) xpath.evaluate("/config/defaultRecordTime", doc, XPathConstants.NODE);
		mc.setDefaultRecordTime(Long.parseLong(node.getTextContent()));
		node = (Node) xpath.evaluate("/config/isDefaultRecordParameters", doc, XPathConstants.NODE);
		mc.setIsDefaultRecordParameters(node.getTextContent());
		node = (Node) xpath.evaluate("/config/isDefaultRecordExceptionDetail", doc, XPathConstants.NODE);
		mc.setDefaultRecordExceptionDetail(Boolean.parseBoolean(node.getTextContent()));
		node = (Node) xpath.evaluate("/config/isDefaultMonitorProxyClass", doc, XPathConstants.NODE);
		mc.setDefaultMonitorProxyClass(Boolean.parseBoolean(node.getTextContent()));
		node = (Node) xpath.evaluate("/config/persistenceType", doc, XPathConstants.NODE);
		mc.setPersistenceType(node.getTextContent());
		for (int i = 0; i < node.getAttributes().getLength(); i++) {
			Node attrNode = node.getAttributes().item(i);
			mc.getPersistenceTypeParam().put(attrNode.getNodeName(), attrNode.getTextContent());
		}

		node = (Node) xpath.evaluate("/config/logInterval", doc, XPathConstants.NODE);
		mc.setLogInterval(Long.parseLong(node.getTextContent()));
		node = (Node) xpath.evaluate("/config/defaultForceRecord", doc, XPathConstants.NODE);
		mc.setDefaultForceRecord(Integer.parseInt(node.getTextContent()));

		nodeList = (NodeList) xpath.evaluate("/config/includes/*", doc, XPathConstants.NODESET);
		for (int i = 0; i < nodeList.getLength(); i++) {
			node = (Node) xpath.evaluate("/config/includes/include[" + (i + 1) + "]", doc, XPathConstants.NODE);
			MonitorItemConfig mic = new MonitorItemConfig();
			NodeList nl = node.getChildNodes();
			for (int j = 0; j < nl.getLength(); j++) {
				Node subNode = nl.item(j);
				if (subNode.getNodeName().equalsIgnoreCase("path")) {
					mic.setPath(subNode.getTextContent());
				}
				if (subNode.getNodeName().equalsIgnoreCase("recordTime")) {
					mic.setRecordTime(Long.parseLong(subNode.getTextContent()));
				}
				if (subNode.getNodeName().equalsIgnoreCase("isRecordParameters")) {
					mic.setIsRecordParameters(subNode.getTextContent());
				}
				if (subNode.getNodeName().equalsIgnoreCase("isRecordExceptionDetail")) {
					mic.setRecordExceptionDetail(Boolean.parseBoolean(subNode.getTextContent()));
				}
				if (subNode.getNodeName().equalsIgnoreCase("forceRecord")) {
					mic.setForceRecord(Integer.parseInt(subNode.getTextContent()));
				}
				if (subNode.getNodeName().equalsIgnoreCase("callType")) {
					mic.setCallType(subNode.getTextContent());
				}
				if (subNode.getNodeName().equalsIgnoreCase("type")) {
					mic.setType(subNode.getTextContent());
				}
				if (subNode.getNodeName().equalsIgnoreCase("isMonitorProxyClass")) {
					mic.setMonitorProxyClass(Boolean.parseBoolean((subNode.getTextContent())));
				}
			}
			if (mic.getPath() == null) {
				throw new ParserConfigurationException("缺少必要的path标签");
			}
			mic.setPath_pattern(buildPathPattern(mic.getPath()));
			mc.getIncludes().add(mic);
		}

		nodeList = (NodeList) xpath.evaluate("/config/excludes/*", doc, XPathConstants.NODESET);
		for (int i = 0; i < nodeList.getLength(); i++) {
			node = (Node) xpath.evaluate("/config/excludes/exclude[" + (i + 1) + "]", doc, XPathConstants.NODE);
			mc.getExcludes().add(node.getTextContent());

			mc.getExclude_pattern().add(buildPathPattern(node.getTextContent()));
		}

		nodeList = (NodeList) xpath.evaluate("/config/startPaths/*", doc, XPathConstants.NODESET);
		for (int i = 0; i < nodeList.getLength(); i++) {
			node = (Node) xpath.evaluate("/config/startPaths/path[" + (i + 1) + "]", doc, XPathConstants.NODE);
			Map<String, Pattern> patternMap = new HashMap<String, Pattern>();
			patternMap.put(node.getTextContent(), buildPathPattern(node.getTextContent()));
			mc.getStartPaths().put(node.getAttributes().getNamedItem("name").getTextContent(), patternMap);
		}

		nodeList = (NodeList) xpath.evaluate("/config/filters/*", doc, XPathConstants.NODESET);
		mc.setFilterChains(new HashMap<Integer, FilterChain>());
		if (nodeList != null) {
			for (int i = 0; i < nodeList.getLength(); i++) {
				node = (Node) xpath.evaluate("/config/filters/filter[" + (i + 1) + "]", doc, XPathConstants.NODE);
				if (node.getTextContent() != null && !node.getTextContent().trim().equalsIgnoreCase("")) {
					try {
						int type = FilterUtils.getFilterType(Class.forName(node.getTextContent()));
						FilterConfig config = new FilterConfig(node.getTextContent(), type);
						if (mc.getFilterChains().get(type) == null) {
							mc.getFilterChains().put(type, FilterChainFactory.getFilterChain());
						}
						mc.getFilterChains().get(type).addFilter(config);
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					} catch (DOMException e) {
						e.printStackTrace();
					}
				}
			}
		}
		cur_mc = mc;
		return mc;
	}

	public static MonitorConfig parseConfig(String path) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
		return parseConfig(MonitorConfig.class.getClassLoader(), path);
	}

	public List<MonitorItemConfig> getIncludes() {
		return includes;
	}

	public void setIncludes(List<MonitorItemConfig> includes) {
		this.includes = includes;
	}

	public List<String> getExcludes() {
		return excludes;
	}

	public void setExcludes(List<String> excludes) {
		this.excludes = excludes;
	}

	public Map<String, Map<String, Pattern>> getStartPaths() {
		return startPaths;
	}

	public void setStartPaths(Map<String, Map<String, Pattern>> startPaths) {
		this.startPaths = startPaths;
	}

	public long getDefaultRecordTime() {
		return defaultRecordTime;
	}

	public void setDefaultRecordTime(long defaultRecordTime) {
		this.defaultRecordTime = defaultRecordTime;
	}

	public String getIsDefaultRecordParameters() {
		return isDefaultRecordParameters;
	}

	public void setIsDefaultRecordParameters(String isDefaultRecordParameters) {
		this.isDefaultRecordParameters = isDefaultRecordParameters;
	}

	public boolean isDefaultRecordExceptionDetail() {
		return isDefaultRecordExceptionDetail;
	}

	public void setDefaultRecordExceptionDetail(boolean isDefaultRecordExceptionDetail) {
		this.isDefaultRecordExceptionDetail = isDefaultRecordExceptionDetail;
	}

	public List<String> getMonitorTypes() {
		return monitorTypes;
	}

	public void setMonitorTypes(List<String> monitorTypes) {
		this.monitorTypes = monitorTypes;
	}

	public List<Pattern> getExclude_pattern() {
		return exclude_pattern;
	}

	public void setExclude_pattern(List<Pattern> exclude_pattern) {
		this.exclude_pattern = exclude_pattern;
	}

	public boolean isDefaultMonitorProxyClass() {
		return isDefaultMonitorProxyClass;
	}

	public void setDefaultMonitorProxyClass(boolean isDefaultMonitorProxyClass) {
		this.isDefaultMonitorProxyClass = isDefaultMonitorProxyClass;
	}

	public static MonitorConfig getCur_mc() {
		return cur_mc;
	}

	public String getPersistenceType() {
		return persistenceType;
	}

	public void setPersistenceType(String persistenceType) {
		this.persistenceType = persistenceType;
	}

	public long getLogInterval() {
		return logInterval;
	}

	public void setLogInterval(long logInterval) {
		this.logInterval = logInterval;
	}

	public int getDefaultForceRecord() {
		return defaultForceRecord;
	}

	public void setDefaultForceRecord(int defaultForceRecord) {
		this.defaultForceRecord = defaultForceRecord;
	}

	public List<Map<String, String>> getMonitorTypeParam() {
		return monitorTypeParam;
	}

	public void setMonitorTypeParam(List<Map<String, String>> monitorTypeParam) {
		this.monitorTypeParam = monitorTypeParam;
	}

	public String getNodeName() {
		return nodeName;
	}

	public void setNodeName(String nodeName) {
		this.nodeName = nodeName;
	}

	public Map<String, String> getPersistenceTypeParam() {
		return persistenceTypeParam;
	}

	public void setPersistenceTypeParam(Map<String, String> persistenceTypeParam) {
		this.persistenceTypeParam = persistenceTypeParam;
	}

	public Map<Integer, FilterChain> getFilterChains() {
		return filterChains;
	}

	public void setFilterChains(Map<Integer, FilterChain> filterChains) {
		this.filterChains = filterChains;
	}

}
